package net.ericaro.neoevents;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The mother class of all "annotated" events.
 * 
 * @author Eric
 * 
 */
public class AbstractEvent {
	

	/**
	 * @author Eric
	 *
	 */
	public static class Bind {

		public enum Type {
			VOID, EVENT
		}
		
		private Method method;
		private Type type;

		/**
		 */
		public Bind(Method event) {
			super();
			this.method = event;
			event.setAccessible(true); //maybe turn it optional based on security issue
			Class[] classes = event.getParameterTypes();
			assert classes.length <= 1 : "Events methods signature are expected to be less or equals to 1";

			if (classes.length == 0)
				type = Type.VOID;
			else {
				type = Type.EVENT;
			}
		}


		public void fire(Object target, Object... events) {
			try {
				switch (type) {
				case VOID:
					method.invoke(target);
					break;
				case EVENT:
					method.invoke(target, events);
					break;
				default:
					break;
				}
				
			} catch (Exception e) {
				throw new RuntimeException("Exception in " + String.valueOf(method), e);
			}

		}
	}

	
	protected Object target;
	protected Map<Class<Annotation>, List<Bind>> binds = new HashMap<Class<Annotation>, List<Bind>>();

	protected AbstractEvent(Object target, Class<?>... annotationsEvent) {

		this.target = target;
		// store the bindings into a map, indexed by method annotation
		for (Class<?> annotation : annotationsEvent) {
			ArrayList<Bind> singleBinds = new ArrayList<Bind>();
			for (Method event : annotatedMethods(target.getClass(), (Class<Annotation>) annotation)) {
				singleBinds.add(new Bind(event));
			}
			binds.put((Class<Annotation>) annotation, singleBinds);
		}
	}

	
	private static <T extends Annotation> List<Method> annotatedMethods(Class<?> target, Class<T> annotationClass) {
		List<Method> methods = new ArrayList<Method>();
		while (target != null) {
			for (Method m : target.getDeclaredMethods()) {
				T annotated = m.getAnnotation(annotationClass);
				if (annotated != null) {
					methods.add(m);
				}
			}
			target = target.getSuperclass();
		}
		return methods;
	}
	
	/** Called by subclasses to fire the actual event.
	 * 
	 * @param annotation
	 * @param event
	 */
	protected <T extends Annotation> void fire(Class<T> annotation, Object... event) {
		for (Bind bind : binds.get(annotation)) {
			bind.fire(target, event);
		}
	}
	
	
	
	
	
	

}